/*-
 * Copyright (c) 2007 Microsoft
 * All rights reserved.
 */

#pragma once
#include "TypeMapper.h"
#include "AsyncResult.h"

namespace Microsoft{
    namespace ComputeCluster{
        namespace MpiNet{
            using namespace System::Threading;

            generic<typename T> ref class SendAsyncResult : public AsyncResult
            {
            private:
                int handle;

                MPI_Request request;
                array<T>^ ArrayData;
                bool disposed;
                unsigned char *buffer;

            internal:
                SendAsyncResult(Comm^ comm, int target, T data, int tag, AsyncCallback^ callback) 
                    : AsyncResult(callback)
                {
                    int ret;
                    MPI_Request request;
                    Type^ t = T::typeid;
                    this->handle = comm->handle;
                    this->disposed = false;

                    if (t->IsPrimitive || t->IsExplicitLayout || t->IsLayoutSequential)
                    {
                        int mpitype = TypeMapper::Get(t);
                        int length = Marshal::SizeOf(data);
                        buffer = new unsigned char[length];
                        Marshal::StructureToPtr(data, *(gcnew IntPtr(buffer)), false);
                        ret = MPI_Isend(&data, 1, mpitype, target, tag, this->handle, &request);
                        if (ret != MPI_SUCCESS)
                            throw gcnew MPIException(ret);
                        this->request = request;
                        return;
                    }

                    if (t->IsSerializable)
                    {
                        int length;
                        System::Runtime::Serialization::Formatters::Binary::BinaryFormatter^ formatter = 
                            gcnew System::Runtime::Serialization::Formatters::Binary::BinaryFormatter();

                        System::IO::MemoryStream^ memoryStream = gcnew System::IO::MemoryStream();
                        formatter->Serialize(memoryStream, (Object^)data);
                        length = (int)memoryStream->Length;
                        ret = MPI_Send(&length, 1, MPI_LONG, target, tag, this->handle);
                        if (ret != MPI_SUCCESS)
                            throw gcnew MPIException(ret);

                        buffer = new unsigned char[length];
                        Marshal::Copy(memoryStream->GetBuffer(), 0, *(gcnew IntPtr(buffer)), length);
                        ret = MPI_Isend(buffer , length, MPI_BYTE, target, tag, this->handle, &request);
                        if (ret != MPI_SUCCESS)
                            throw gcnew MPIException(ret);
                        this->request = request;
                        return;
                    }

                    throw gcnew System::NotImplementedException();
                }

                SendAsyncResult(Comm^ comm, int target, array<T>^ data, int tag, int index, int length, AsyncCallback^ callback)
                    : AsyncResult(callback)
                {
                    int ret;
                    MPI_Request request;
                    this->handle = comm->handle;
                    this->ArrayData = data;
                    this->disposed = false;

                    if (T::typeid->IsPrimitive)
                    {
                        pin_ptr<T> datap = &data[index];
                        int mpitype = TypeMapper::Get(T::typeid);
                        ret = MPI_Isend(datap, length, mpitype, target, tag, this->handle, &request);
                        if (ret != MPI_SUCCESS)
                            throw gcnew MPIException(ret);
                        this->request = request;
                        return;
                    }

                    throw gcnew System::NotImplementedException();
                }

            protected:
                virtual void MyDispose(bool disposing) override
                {
                    if (!disposed)
                    {
                        if (disposing)
                        {
                            if (buffer != NULL)
                            {
                                delete[] buffer;
                                buffer = NULL;
                            }
                        }
                        disposed = true;
                    }
                }
            };
        }
    }
}